package com.zheng.coderepo.idcard;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.util.Calendar;

/**
* Created by zhangchaozheng on 17-2-21.
*/
public class IdCardUtils {
    /**
     * 省、直辖市代码表
     */
    public static final String cityCode[] = {
            "11", "12", "13", "14", "15", "21", "22", "23", "31", "32", "33", "34", "35", "36", "37", "41",
            "42", "43", "44", "45", "46", "50", "51", "52", "53", "54", "61", "62", "63", "64", "65", "71",
            "81", "82", "91"
    };

    /**
     * 每位加权因子
     */
    public static final int Wi[] = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};

    /**
     * 第18位校检码
     */
    public static final String ValCodeArr[] = {"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"};

    /**
     * 省、直辖市代码表
     */
    public static final String cityCode_encrypt[] = {
            "15", "16", "17", "18", "19", "20", "99", "23", "24", "01", "02", "03", "44", "45", "46", "47",
            "48", "49", "61", "62", "63", "95", "96", "97", "64", "67", "68", "69", "51", "52", "53", "54",
            "55", "56", "88"
    };

    /**
     * 第18位校检码
     */
    public static final String ValCodeArr_encrypt[] = {"5", "8", "4", "1", "2", "3", "7", "12", "13", "15", "21"};

    private static final int FROM_YEAR = 1800;
    /**
     * 检查身份证是否合法
     * @param idNo
     * @return
     */
    public static boolean checkIdNo(String idNo) {
        // 1.检查身份证长度
        if (idNo.length() != 18) {
            throw new IllegalArgumentException("身份证号码长度应该为18位。");
        }

        // 2.检查身份证号是否符合数字规则
        String Ai = idNo.substring(0, 17);
        if (StringUtils.isNumeric(Ai) == false) {
            throw new IllegalArgumentException("18位号码除最后一位外，都应为数字。");
        }

        // 3.检查出年日期是否有效
        String strYear = Ai.substring(6, 10);// 年份
        String strMonth = Ai.substring(10, 12);// 月份
        String strDay = Ai.substring(12, 14);// 月份
        Calendar cal = Calendar.getInstance();
        int currentYear = cal.get(Calendar.YEAR);
        int year = Integer.parseInt(strYear);
        if ((currentYear - year) < 0 || (currentYear - year) > 150) {
            throw new IllegalArgumentException("身份证出生日期年份无效。");
        }

        int month = Integer.parseInt(strMonth);
        if (month < 0 || month > 12) {
            throw new IllegalArgumentException("身份证出生日期月份无效。");
        }

        int day = Integer.parseInt(strDay);
        if (day < 0 || day > 31) {
            throw new IllegalArgumentException("身份证出生日期的天无效。");
        }

        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        sdf.setLenient(false);
        try {
            sdf.parse(strYear + strMonth + strDay);
        } catch (Exception e) {
            throw new IllegalArgumentException("身份证出生日期无效。");
        }

        // 4.地区码是否有效
        if (!ArrayUtils.contains(cityCode, Ai.substring(0, 2))) {
            throw new IllegalArgumentException("身份证地区编码错误。");
        }

        // 5.验证最后一位校验码
        int totalAiWi = 0;
        for (int i = 0; i < 17; i++) {
            totalAiWi = totalAiWi + Integer.parseInt(String.valueOf(Ai.charAt(i))) * Wi[i];
        }
        int modValue = totalAiWi % 11;
        String strVerifyCode = ValCodeArr[modValue];
        Ai = Ai + strVerifyCode;

        if (Ai.equals(idNo) == false) {
            throw new IllegalArgumentException("身份证无效，不是合法的身份证号码");
        }
        return true;
    }

    public static String encrypt(String idNo) {
        //检查证件号的合法性
        try {
            checkIdNo(idNo);
        } catch (Exception e) {
            return "";
        }

        String city = idNo.substring(0, 2);//city
        String addr = idNo.substring(2, 6);//addr
        String year = idNo.substring(6, 10);// 年份
        String month = idNo.substring(10, 12);// 月份
        String day = idNo.substring(12, 14);// 月份
        String seq = idNo.substring(14, 17);//序号
        String valCode = idNo.substring(17, 18);//检验位

        String cityChange = new BigInteger(getCityChange(city), 10).toString(36);
        String addrChange = new BigInteger(addr, 10).toString(36);
        String yearChange =  new BigInteger((Integer.parseInt(year) - FROM_YEAR) + "", 10).toString(36);
        String monthChange = new BigInteger(month, 10).toString(36);
        String dayChange = new BigInteger(day, 10).toString(36);
        String seqChange = new BigInteger(seq, 10).toString(36);
        String valCodeChange = new BigInteger(getValCodeChange(valCode), 10).toString(36);

        return "" +
                //保持2位
                leftPad(cityChange, 2) +
                //保持4位
                leftPad(addrChange, 4) +
                //保持2位,使用36进制保存可以支持1295年
                leftPad(yearChange, 2) +
                //使用1位
                leftPad(monthChange, 1) +
                //使用1位
                leftPad(dayChange, 1) +
                //保持2位
                leftPad(seqChange, 2) +
                //保持1位
                valCodeChange;
    }

    public static String decrypt(String pk) {
        String city = pk.substring(0, 2);//city
        String addr = pk.substring(2, 6);//addr
        String year = pk.substring(6, 8);// 年份
        String month = pk.substring(8, 9);// 月份
        String day = pk.substring(9, 10);// 月份
        String seq = pk.substring(10, 12);//序号
        String valCode = pk.substring(12, 13);//检验位

        //还原2位
        String cityChange = getRealCity(new BigInteger(city, 36).toString(10));
        //还原4位
        String addrChange = new BigInteger(addr, 36).toString(10);
        //还原4位
        String yearChange = new BigInteger(year, 36).add(BigInteger.valueOf(FROM_YEAR)).toString(10);
        //还原2位
        String monthChange = new BigInteger(month, 36).toString(10);
        //还原2位
        String dayChange = new BigInteger(day, 36).toString(10);
        //还原3位
        String seqChange = new BigInteger(seq, 36).toString(10);
        //还原1位
        String valCodeChange = getRealValCode(new BigInteger(valCode, 36).toString(10));

        return cityChange +
                leftPad(addrChange, 4) +
                leftPad(yearChange, 2) +
                leftPad(monthChange, 2) +
                leftPad(dayChange, 2) +
                leftPad(seqChange, 3) +
                valCodeChange;
    }


    private static String getValCodeChange(String valCode) {
        int i = ArrayUtils.indexOf(ValCodeArr, valCode);
        return ValCodeArr_encrypt[i];
    }


    private static String getRealValCode(String valCode) {
        int i = ArrayUtils.indexOf(ValCodeArr_encrypt, valCode);
        return ValCodeArr[i];
    }

    private static String getCityChange(String city) {
        int i = ArrayUtils.indexOf(cityCode, city);
        return cityCode_encrypt[i];
    }

    private static String getRealCity(String city) {
        int i = ArrayUtils.indexOf(cityCode_encrypt, city);
        return cityCode[i];
    }

    private static String leftPad(String addrChange, int size) {
        return StringUtils.leftPad(addrChange, size, "0");
    }

}
